package com.qianheng.kit.base.util;

import com.qianheng.kit.common.dto.BaseDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.UnaryOperator;

@Slf4j
public class DesensitizationUtil {

    /**
     * 脱敏
     *
     * @param obj
     * @param config 配置
     * @param logSb  日志
     * @param n      递归深度
     */
    public static void desensitization(Object obj, Map<String, String> config, StringBuilder logSb, int n) {
        try {
            Map<String, StrategyEnum> strategys = initDesensitizationConfig(config);
            deepDesensitization(obj, strategys, logSb, n);
        } catch (Throwable e) {
            log.info("脱敏异常,objName={}, map={}, log={}, n={}", obj.getClass().getName(), config, logSb, n, e);
        }
    }

    /**
     * 脱敏类型白名单
     *
     * @param obj
     * @return
     */
    public static boolean isDesensitizationType(Object obj) {
        if (obj == null) {
            return false;
        }
        return obj instanceof BaseDTO;
    }

    private static int deepDesensitization(Object obj, Map<String, StrategyEnum> config, StringBuilder logSb, int n) throws InvocationTargetException, IllegalAccessException {
        n--;
        if (n < 0) {
            return -1;
        }
        logSb.append("\"").append(obj.getClass().getSimpleName())
                .append("|").append(n)
                .append("\":").append("{");

        //全部get方法
        Set<Method> getterMethods = ReflectionUtils.getAllMethods(obj.getClass(), method -> method.getName().startsWith("get") && method.getParameterCount() == 0);
        for (Method getterMethod : getterMethods) {
            if (!getterMethod.getName().startsWith("get")) {
                continue;
            }
            Object getterObj = getterMethod.invoke(obj, null);
            if (getterObj == null) {
                continue;
            }
            if (getterObj instanceof Collection) {
                Collection c = (Collection) getterObj;
                Object first = c.stream().findFirst().orElse(null);
                if (!isDesensitizationType(first)) {
                    continue;
                }
                for (Object o1 : c) {
                    deepDesensitization(o1, config, logSb, n);
                }
            } else if (getterObj instanceof Map) {
                Map m = (Map) getterObj;
                Object first = Optional.ofNullable(m.values()).orElse(Collections.emptySet()).stream().findFirst().orElse(null);
                if (!isDesensitizationType(first)) {
                    continue;
                }
                for (Object key : m.keySet()) {
                    Object value = m.get(key);
                    if (value instanceof String || value instanceof Number) {
                        doOneMapPut(key, value, m, config, logSb);
                    }
                    deepDesensitization(value, config, logSb, n);

                }
            } else if (isDesensitizationType(getterObj)) {
                //
                deepDesensitization(getterObj, config, logSb, n);
            } else {
                doOneDesensitization(obj, getterMethod, getterObj, config, logSb);
            }
        }

        logSb.append("}");
        return n;
    }

    /**
     * 一个字段脱敏
     *
     * @param obj          主体对象
     * @param getterMethod obj的一个get方法
     * @param getterObj    该get方法的返回对象
     * @param config       脱敏配置
     * @param logSb        日志
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static void doOneDesensitization(Object obj, Method getterMethod, Object getterObj, Map<String, StrategyEnum> config, StringBuilder logSb) throws InvocationTargetException, IllegalAccessException {
        if (!config.keySet().contains(getterMethod.getName())) {
            return;
        }
        String setterName = StringUtils.replace(getterMethod.getName(), "g", "s", 1);
        Method setterMethod = null;
        try {
            setterMethod = obj.getClass().getMethod(setterName, getterObj.getClass());
        } catch (NoSuchMethodException e) {

        }
        if (setterMethod == null) {
            return;
        }
        StrategyEnum strategyEnum = config.get(getterMethod.getName());
        if (strategyEnum.getParamClass() != null && !strategyEnum.getParamClass().getName().equals(getterObj.getClass().getName())) {
            return;
        }
        //执行
        setterMethod.invoke(obj, strategyEnum.getUnaryOperator().apply(getterObj));
        //日志
        logSb.append("\"").append(setterName).append("\"")
                .append(":")
                .append("\"").append(strategyEnum).append("\",");
    }

    /**
     * 处理map的一个put请求
     *
     * @param key
     * @param value
     * @param m
     * @param config
     * @param logSb
     */
    private static void doOneMapPut(Object key, Object value, Map m, Map<String, StrategyEnum> config, StringBuilder logSb) {
        if (key == null || value == null || m == null) {
            return;
        }
        StrategyEnum strategyEnum = Optional.ofNullable(config.entrySet()).orElse(Collections.emptySet())
                .stream()
                .filter(entry -> key.equals(lowerFirst(entry.getKey().substring(3))))
                .map(Map.Entry::getValue)
                .findFirst()
                .orElse(null);
        if (strategyEnum == null) {
            return;
        }
        if (strategyEnum.getParamClass() != null && !strategyEnum.getParamClass().getName().equals(value.getClass().getName())) {
            return;
        }
        m.put(key, strategyEnum.getUnaryOperator().apply(value));
        //日志
        logSb.append("\"Map.").append(key).append("\"")
                .append(":")
                .append("\"").append(strategyEnum).append("\",");
    }

    /**
     * @param desensitizationParameters
     * @return <待脱敏字段的get方法名称,脱敏策略枚举>
     */
    private static Map<String, StrategyEnum> initDesensitizationConfig(Map<String, String> desensitizationParameters) {
        Map<String, StrategyEnum> config = new HashMap<>(desensitizationParameters.size());
        for (Map.Entry<String, String> entry : desensitizationParameters.entrySet()) {
            config.put("get" + upperFirst(entry.getKey()), StrategyEnum.getEnumOrDefault(entry.getValue()));
        }
        return config;
    }


    private static String lowerFirst(String s) {
        return s != null && s.length() != 0 ? Character.toLowerCase(s.charAt(0)) + s.substring(1) : s;
    }

    private static String upperFirst(String s) {
        return s != null && s.length() != 0 ? Character.toUpperCase(s.charAt(0)) + s.substring(1) : s;
    }

    /**
     * 修改策略枚举
     */
    public enum StrategyEnum {
        DEFAULT("默认", o -> {
            return o;
        }),
        SETNULL("置空", o -> {
            return null;
        }),
        DESENSITIZATION_MIDDLE("中间脱敏", String.class, o -> {
            if (o == null) {
                return null;
            }
            if (o instanceof String) {
                String s = (String) o;
                return StringUtils.overlay(s, fillDesensitizationStr("*", s.length() - 2), 1, s.length() - 1);
            } else {
                return o;
            }
        });


        private String desc;
        private Class paramClass;
        private UnaryOperator<Object> unaryOperator;

        StrategyEnum(String desc, UnaryOperator<Object> unaryOperator) {
            this.desc = desc;
            this.unaryOperator = unaryOperator;
        }

        StrategyEnum(String desc, Class paramClass, UnaryOperator<Object> unaryOperator) {
            this.desc = desc;
            this.paramClass = paramClass;
            this.unaryOperator = unaryOperator;
        }

        /**
         * 根据name获取枚举,如果获取不到就返回DEFAULT
         *
         * @param name
         * @return
         */
        public static StrategyEnum getEnumOrDefault(String name) {
            try {
                return StrategyEnum.valueOf(name);
            } catch (Exception e) {
                return DEFAULT;
            }
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public Class getParamClass() {
            return paramClass;
        }

        public void setParamClass(Class paramClass) {
            this.paramClass = paramClass;
        }

        public UnaryOperator<Object> getUnaryOperator() {
            return unaryOperator;
        }

        public void setUnaryOperator(UnaryOperator<Object> unaryOperator) {
            this.unaryOperator = unaryOperator;
        }

        private static String fillDesensitizationStr(String str, int count) {
            if (count <= 0) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            int i = 0;
            while (i < count) {
                i++;
                sb.append(str);
            }
            return sb.toString();
        }
    }
}
